﻿using System;
using System.Collections.Generic;
using ParserGenerator.GrammarConstruction;

namespace ParserGenerator.ParserConstruction
{
    [Serializable]
    public sealed class ParserAutomatonItemLookaheadSetsCache
    {
        private readonly ParserAutomatonGotosTable _gotos;
        private readonly ParserAutomatonItemLookaheadSetsTable _lalrLookaheads;
        private readonly ParserAutomatonItemLookaheadSetsTable _ielrLookaheads;
        public ParserAutomatonType AutomatonType { get; }

        internal ParserAutomatonItemLookaheadSetsCache(ParserAutomatonGotosTable gotos, ParserAutomatonType automatonType)
        {
            if (gotos == null)
                throw new ArgumentNullException(nameof(gotos));
            _gotos = gotos;
            _lalrLookaheads = new ParserAutomatonItemLookaheadSetsTable();
            _ielrLookaheads = new ParserAutomatonItemLookaheadSetsTable();
            AutomatonType = automatonType;
        }

        private void EnsureLalrItemLookaheadSet(ParserAutomatonState state, ParserAutomatonItem item)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            if (item == null)
                throw new ArgumentNullException(nameof(item));
            ParserAutomatonItemLookaheadSet lookahadSet;
            if (_lalrLookaheads.TryGetLookaheadSet(state, item, out lookahadSet))
                return;
            lookahadSet = _lalrLookaheads.GetLookaheadSet(state, item);
            if (item.IsAccept)
            {
                GrammarGoalDescriptor goal = item.Production.Goal;
                lookahadSet.UnionWith(goal.EndMarkers);
            }
            else if (item.DotPosition == 1)
            {
                foreach (ParserAutomatonState pred in state.Predecessors)
                {
                    GrammarNonterminalSymbol lhs = item.Production.Owner;
                    ParserAutomatonGoto g = _gotos.GetGoto(pred, lhs);
                    lookahadSet.UnionWith(_gotos.GotoFollows[g]);
                }
            }
            else
            {
                foreach (ParserAutomatonState pred in state.Predecessors)
                {
                    EnsureLalrItemLookaheadSet(pred, item.Predecessor);
                    var predLookaheadSet = _lalrLookaheads.GetLookaheadSet(pred, item.Predecessor);
                    lookahadSet.UnionWith(predLookaheadSet);
                }
            }
        }

        public bool LalrItemHasLookahead(ParserAutomatonState state, ParserAutomatonItem item, GrammarTerminalSymbol lookahead)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            if (item == null)
                throw new ArgumentNullException(nameof(item));
            if (lookahead == null)
                throw new ArgumentNullException(nameof(lookahead));
            EnsureLalrItemLookaheadSet(state, item);
            var lookaheadSet = _lalrLookaheads.GetLookaheadSet(state, item);
            return lookaheadSet.Contains(lookahead);
        }

        public ParserAutomatonContributionColumn ComputeLalrLhsContributionColumn(ParserAutomatonState state, GrammarNonterminalSymbol lhs, GrammarTerminalSymbol token)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            if (lhs == null)
                throw new ArgumentNullException(nameof(lhs));
            if (token == null)
                throw new ArgumentNullException(nameof(token));
            ParserAutomatonGoto g = _gotos.GetGoto(state, lhs);
            if (_gotos.AlwaysFollows.ContainsKey(g) && _gotos.AlwaysFollows[g].Contains(token))
                return null;
            var column = new ParserAutomatonContributionColumn();
            if (_gotos.FollowKernelItems.ContainsKey(g))
                foreach (ParserAutomatonItem item in _gotos.FollowKernelItems[g])
                    if (LalrItemHasLookahead(state, item, token))
                        column.Add(item);
            return column;
        }

        private void ComputeIelrGotoFollowSet(ParserAutomatonState state, GrammarNonterminalSymbol symbol, ISet<GrammarTerminalSymbol> followSet, ParserAutomatonItemLookaheadSets lookaheadSets)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            if (symbol == null)
                throw new ArgumentNullException(nameof(symbol));
            if (followSet == null)
                throw new ArgumentNullException(nameof(followSet));
            ParserAutomatonGoto g = _gotos.GetGoto(state, symbol);
            if (_gotos.AlwaysFollows.ContainsKey(g))
                followSet.UnionWith(_gotos.AlwaysFollows[g]);
            if (lookaheadSets != null && _gotos.FollowKernelItems.ContainsKey(g))
                foreach (ParserAutomatonItem item in _gotos.FollowKernelItems[g])
                    if (lookaheadSets.ContainsKey(item))
                        followSet.UnionWith(lookaheadSets[item]);
        }

        public ParserAutomatonItemLookaheadSets ComputeIelrLookaheadSets(ParserAutomatonState source, ParserAutomatonState target, ParserAutomatonItemLookaheadSets lookaheadFilter)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (target == null)
                throw new ArgumentNullException(nameof(target));
            if (lookaheadFilter == null)
                if (AutomatonType != ParserAutomatonType.LR)
                    throw new ArgumentNullException(nameof(lookaheadFilter));
            ParserAutomatonItemLookaheadSets sourceLookaheadSets;
            _ielrLookaheads.TryGetValue(source, out sourceLookaheadSets);
            var targetLookaheadSets = new ParserAutomatonItemLookaheadSets();
            foreach (ParserAutomatonItem item in target.Core)
            {
                if (lookaheadFilter != null)
                {
                    ParserAutomatonItemLookaheadSet itemFilter;
                    if (!lookaheadFilter.TryGetValue(item, out itemFilter))
                        continue;
                    if (itemFilter == null || itemFilter.Count <= 0)
                        continue;
                }
                var lookaheadSet = new ParserAutomatonItemLookaheadSet(item);
                if (item.IsAccept)
                {
                    GrammarGoalDescriptor goal = item.Production.Goal;
                    lookaheadSet.UnionWith(goal.EndMarkers);
                }
                else if (item.DotPosition == 1)
                {
                    GrammarProduction prod = item.Production;
                    GrammarNonterminalSymbol lhs = prod.Owner;
                    ComputeIelrGotoFollowSet(source.LR0Isocore, lhs, lookaheadSet.UnderlyingSet, sourceLookaheadSets);
                }
                else if (sourceLookaheadSets != null)
                {
                    ParserAutomatonItemLookaheadSet sourceLookaheadSet;
                    if (sourceLookaheadSets.TryGetValue(item.Predecessor, out sourceLookaheadSet))
                        lookaheadSet.UnionWith(sourceLookaheadSet);
                }
                if (lookaheadFilter != null)
                    lookaheadSet.FilterWith(lookaheadFilter[item]);
                targetLookaheadSets.Add(item, lookaheadSet);
            }
            return targetLookaheadSets;
        }

        public ParserAutomatonReductionLookaheadSetsTable SaveIelrReductionLookaheadSets(ParserAutomatonStateTable states)
        {
            if (states == null)
                throw new ArgumentNullException(nameof(states));
            var table = new ParserAutomatonReductionLookaheadSetsTable();
            foreach (ParserAutomatonState state in states)
            {
                if (state.IsConsistent)
                    continue;
                var sets = new ParserAutomatonReductionLookaheadSets(state, true);
                ParserAutomatonItemLookaheadSets lookaheadSets = _ielrLookaheads[state];
                foreach (ParserAutomatonReduction r in state.Reductions.Values)
                {
                    GrammarProduction prod = r.Production;
                    if (prod.IsEmpty)
                    {
                        var set = new ParserAutomatonReductionLookaheadSet(r);
                        ComputeIelrGotoFollowSet(state.LR0Isocore, prod.Owner, set.UnderlyingSet, lookaheadSets);
                        sets.Add(r.Production, set);
                    }
                    else
                    {
                        ParserAutomatonItem item = prod.ReduceItem;
                        var lookaheadSet = lookaheadSets[item];
                        var set = new ParserAutomatonReductionLookaheadSet(r, lookaheadSet.UnderlyingSet);
                        sets.Add(r.Production, set);
                    }
                }
                table.Add(state, sets);
            }
            return table;
        }

        internal void AddIelrLookaheadSets(ParserAutomatonState state, ParserAutomatonItemLookaheadSets sets)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            if (sets == null)
                throw new ArgumentNullException(nameof(sets));
            sets.State = state;
            foreach (var t in sets)
                t.Value.State = state;
            _ielrLookaheads.Add(state, sets);
        }

        internal ParserAutomatonItemLookaheadSets GetLookaheadSets(ParserAutomatonState state)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            ParserAutomatonItemLookaheadSets sets;
            _ielrLookaheads.TryGetValue(state, out sets);
            return sets;
        }

        internal ParserAutomatonItemLookaheadSet GetLookaheadSet(ParserAutomatonState state, ParserAutomatonItem item)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            if (item == null)
                throw new ArgumentNullException(nameof(item));
            return _ielrLookaheads.GetLookaheadSet(state, item);
        }
    }
}
